package com.jxsr.aspect;

import com.jxsr.algorithm.LeakyBucketRateLimiter;
import com.jxsr.annotation.Limitation;
import com.jxsr.constant.BaseConstant;
import com.jxsr.exception.LimitationException;
import com.jxsr.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Description : 限制接口访问次数
 * @Author : Bruce Lee
 * @CreateTime : 2024/12/17
 */

@Slf4j
@Aspect
@Component
public class LimitationAspect {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 限制接口访问次数：使用redis 记录
     */

    @Before(value = "@annotation(com.jxsr.annotation.Limitation)")
    public void before(JoinPoint joinPoint) {

        /**
         * 获取方法上的注解
         */
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Limitation limitation = methodSignature.getMethod().getAnnotation(Limitation.class);
        int count = limitation.count();
        Integer time = limitation.time();
        int capacity = limitation.capacity();
        double rate = limitation.rate();
        // 获取方法名
        String methodName = methodSignature.getMethod().getName();

        // key格式：前缀：方法名：计算
        StringBuffer key = new StringBuffer(BaseConstant.PREFIX).append(methodName);

        // 方法一：
//        if (redisUtil.hasKey(key.toString())) {
//            // 当前已访问次数
//            int sum = (int) redisUtil.getCacheObject(key.toString());
//            if (sum >= count) {
//                log.error("超过请求次数，请稍后再试, 过期时间：{}", redisUtil.getExpire(key.toString()));
//                throw new LimitationException("超过请求次数，请稍后再试");
//            } else {
//                // 修改redis的vaule，但不修改过期时间
//                // redisUtil.getExpire获取的是分钟
//                Date date = new Date(System.currentTimeMillis() + redisUtil.getExpire(key.toString()) * 1000);
//                redisUtil.setCacheObject(key.toString(), sum + 1);
//                // 为什么一直是当前时间: 因为你在上一步刚没有设置过期时间
//                redisUtil.expireAt(key.toString(), date);
//                log.info("key剩余过期时间：{}", redisUtil.getExpire(key.toString()));
//            }
//        } else {
//            // 第一次访问
//            redisUtil.setCacheObject(key.toString(), 1, time, TimeUnit.MILLISECONDS);
//        }



         // 方法二：推荐
         // 模糊查询redis的key：keys *keyword*
//        Collection<String> keys = redisUtil.keys("*" + key.toString() + "*");
//        int size = keys.size() + 1;
//        if (size > count) {
//            log.error("超过请求次数，请稍后再试; 请求方法：{}", methodName);
//            throw new LimitationException("超过请求次数，请稍后再试");
//        } else {
//            /**
//             * 区间范围内限流
//             */
//            redisUtil.setCacheObject(key.toString()+size, size, time, TimeUnit.MILLISECONDS);
//        }

        // 方法三
//        if (redisUtil.hasKey(key.toString())){
//            // 不是第一次访问
//            int sum = (int) redisUtil.getCacheObject(key.toString());
//            if (sum >= count){
//                log.error("超过请求次数，请稍后再试, 过期时间：{}", redisUtil.getExpire(key.toString()));
//                throw new LimitationException("超过请求次数，请稍后再试");
//            }else{
//                // value自增同时不影响过期时间
//                redisUtil.incr(key.toString());
//            }
//        }else{
//            // 第一次访问
//            redisUtil.setCacheObject(key.toString(), 1, time, TimeUnit.MILLISECONDS);
//        }


        // 方法四
//        if (!checkLimitation(key.toString(), System.currentTimeMillis(), count, time)){
//            log.error("超过请求次数，请稍后再试, 过期时间");
//            throw new LimitationException("超过请求次数，请稍后再试");
//        }

        // 方法五：漏桶算法


        LeakyBucketRateLimiter.tryAcquire(capacity, rate);




    }

    public boolean checkLimitation(String key, long time, int count, Integer interval){
        List<Object> cacheList = redisUtil.getCacheList(key);

        // 删除过期的时间戳
        cacheList.removeIf(o -> (long)o + interval < time);

        if (cacheList.size() >= count){
            // 超过限制次数
            return false;
        }else {
            redisUtil.addList(key, time);
            return true;
        }
    }

}
